/************************************************************************************
   Copyright (C) 2020,2023 MariaDB Corporation AB

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not see <http://www.gnu.org/licenses>
   or write to the Free Software Foundation, Inc.,
   51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/


#ifndef _MARIADBCONNECTION_H_
#define _MARIADBCONNECTION_H_

#include <mutex>

#include "MariaDbStatement.h"
//#include "ClientSidePreparedStatement.h"

#include "Connection.hpp"
#include "Statement.hpp"
#include "Protocol.h"

#include "MariaDbPooledConnection.h"

#include "Consts.h"
#include "pool/GlobalStateInfo.h"
#include "options/Options.h"
#include "cache/CallableStatementCache.h"
#include "failover/FailoverProxy.h"


namespace sql
{
namespace mariadb
{
  class CallableStatementCache;
  class MariaDbPooledConnection;
  class ExceptionFactory;

  enum ConnectionState
  {
    STATE_NETWORK_TIMEOUT= 1,
    STATE_DATABASE= 2,
    STATE_READ_ONLY= 4,
    STATE_AUTOCOMMIT= 8,
    STATE_TRANSACTION_ISOLATION= 16
  };

class MariaDbConnection  : public Connection
{
    static std::shared_ptr<sql::mariadb::Logger> logger ; /*LoggerFactory.getLogger(MariaDbConnection.class)*/

    Shared::Protocol protocol;
    Shared::Options options;

    /* We don't want copy constructing*/
    MariaDbConnection(const MariaDbConnection& other)= delete;
    Shared::ExceptionFactory exceptionFactory;

public:
  Shared::mutex lock; /* TODO: Public? Really? */
  std::unique_ptr<MariaDbPooledConnection> pooledConnection;
//protected:
  bool nullCatalogMeansCurrent;
private:
  std::unique_ptr<CallableStatementCache> callableStatementCache;
  volatile int32_t lowercaseTableNames= -1;
  bool _canUseServerTimeout;
  bool sessionStateAware;
  int32_t stateFlag= 0 ;
  int32_t defaultTransactionIsolation= 0;
  int32_t savepointCount= 0;
  bool warningsCleared= true;

public:
  MariaDbConnection(Shared::Protocol& protocol);
  static MariaDbConnection* newConnection(UrlParser& urlParser, GlobalStateInfo* globalInfo);
  static SQLString quoteIdentifier(const SQLString& string);
  static SQLString unquoteIdentifier(SQLString& string);
  ~MariaDbConnection();
//protected:
  Shared::Protocol& getProtocol();

public:
  Statement* createStatement();
  Statement* createStatement(int32_t resultSetType,int32_t resultSetConcurrency);
  Statement* createStatement( int32_t resultSetType,int32_t resultSetConcurrency,int32_t resultSetHoldability);

private:
  void checkConnection();

public:
  ClientSidePreparedStatement* clientPrepareStatement(const SQLString& sql);
  ServerSidePreparedStatement* serverPrepareStatement(const SQLString& sql);
  PreparedStatement* prepareStatement(const SQLString& sql);
  PreparedStatement* prepareStatement(const SQLString& sql,int32_t resultSetType,int32_t resultSetConcurrency);
  PreparedStatement* prepareStatement(const SQLString& sql, int32_t resultSetType, int32_t resultSetConcurrency,
                                              int32_t resultSetHoldability);
  PreparedStatement* prepareStatement(const SQLString& sql,int32_t autoGeneratedKeys);
  PreparedStatement* prepareStatement(const SQLString& sql,int32_t* columnIndexes);
  PreparedStatement* prepareStatement(const SQLString& sql,const SQLString* columnNames);

private:
  PreparedStatement* internalPrepareStatement(const SQLString& sql, int32_t resultSetScrollType, int32_t resultSetConcurrency,
                                                      int32_t autoGeneratedKeys);
public:
  CallableStatement* prepareCall(const SQLString& sql);
  CallableStatement* prepareCall(const SQLString& sql, int32_t resultSetType, int32_t resultSetConcurrency);
  CallableStatement* prepareCall(const SQLString& sql, int32_t resultSetType, int32_t resultSetConcurrency,
                                          int32_t resultSetHoldability);
  private: CallableStatement* createNewCallableStatement(SQLString query, SQLString& procedureName, bool isFunction, SQLString& databaseAndProcedure,
                                                        SQLString& database, SQLString& arguments, int32_t resultSetType, int32_t resultSetConcurrency,
                                                        Shared::ExceptionFactory& expFactory);
public:
  SQLString nativeSQL(const SQLString& sql);
  bool getAutoCommit();
  void setAutoCommit(bool autoCommit);
  void commit();
  void rollback();
  void rollback(const Savepoint* savepoint);
  void close();
  bool isClosed();
  DatabaseMetaData* getMetaData();
  bool isReadOnly();
  void setReadOnly(bool readOnly);
  SQLString getCatalog();
  void setCatalog(const SQLString& catalog);
  bool isServerMariaDb();
  bool versionGreaterOrEqual(int32_t major,int32_t minor,int32_t patch);
  int32_t getTransactionIsolation();
  void setTransactionIsolation(int32_t level);
  SQLWarning* getWarnings();
  void clearWarnings();
  void reenableWarnings();
  int32_t getHoldability();
  void setHoldability(int32_t holdability);
  Savepoint* setSavepoint();
  Savepoint* setSavepoint(const SQLString& name);
  void releaseSavepoint(const Savepoint* savepoint);

  sql::Connection* setClientOption(const SQLString& name, void* value);
  sql::Connection* setClientOption(const SQLString& name, const SQLString& value);
  void getClientOption(const SQLString& n, void* v);
  SQLString getClientOption(const SQLString& n);

  Clob* createClob();
  Blob* createBlob();
  NClob* createNClob();
  SQLXML* createSQLXML();
#ifdef JDBC_SPECIFIC_TYPES_IMPLEMENTED
  sql::Array* createArrayOf(const SQLString& typeName,const sql::Object* elements);
  sql::Struct* createStruct(const SQLString& typeName,const sql::Object* attributes);
#endif

  bool isValid(int32_t timeout);
  bool isValid();

private:
  void checkClientClose(const SQLString& name);
  void checkClientReconnect(const SQLString& name);
  void checkClientValidProperty(const SQLString& name);
  SQLString buildClientQuery(const SQLString& name,const SQLString& value);

public:
  void setClientInfo(const SQLString& name,const SQLString& value);
  void setClientInfo(const Properties& properties);
  Properties getClientInfo();
  SQLString getClientInfo(const SQLString& name);


#ifdef WE_NEED_IT_AND_HAVE_FOUND_THE_WAY_TO_IMPLEMENT_IT
  template <class T >T unwrap();
  bool isWrapperFor();
#endif

  SQLString getUsername();
  SQLString getHostname();
  int32_t getPort();

protected:
  bool getPinGlobalTxToPhysicalConnection();
public:
  void setHostFailed();
  int32_t getLowercaseTableNames();
  void abort(sql::Executor* executor);
  int32_t getNetworkTimeout();
  SQLString getSchema();
  void setSchema(const SQLString& arg0);
  void setNetworkTimeout(Executor* executor,int32_t milliseconds);
  int64_t getServerThreadId();
  bool canUseServerTimeout();
  void setDefaultTransactionIsolation(int32_t defaultTransactionIsolation);
  void reset();
  bool reconnect();
  bool includeDeadLockInfo();
  bool includeThreadsTraces();
  CallableParameterMetaData* getInternalParameterMetaData(const SQLString& procedureName, const SQLString& databaseName, bool isFunction);
};

}
}
#endif
